
ON WRITING A VISTA PLUGIN


The ViSta plugin interface consists of two short pieces of code. 

  a) One piece is the PLUGIN LOADER FUNCTION, which comprises the entire executable contents of the PLUGIN LOADER FILE. The loader file is located in ViSta's PLUGINS directory. This is the file that interfaces your plugin's code with ViSta and its plugin system. All files in the PLUGINS directory are automatically loaded in by ViSta, and all these files must be PLUGIN LOADER files only. 

  b) The other piece is the plugin PLUGIN CONSTRUCTOR FUNCTION. This function is an even smaller piece of code which comprises a part of the executable contents of the PLUGIN CONSTRUCTOR FILE. This function is the main entry point to the rest of the code. The constructor file must contain, in the stated order:
       1 >> your plugin's constructor function
       2 >> your plugin's defproto statement
       3 >> your plugin's isnew method
       3 >> load functions that load additional code
       3 >> additional code if appropriate
The order of the last three is not important, but they must all follow the defproto.

The main body of your plugin's code resides in a subdirectory of the PLUGIN directory. The code in a subdirectory is not automatically loaded by ViSta: You have to write the code to make it load.


EXAMPLES OF PLUGINS

It is recommended that you read the code for the frequency analysis plugin, since it is simple and short and since it is used to illustrate the plugin interface. The Log Linear analysis, Homogeneity analysis and both MDS plugins are also written according to the rules given here, and serve as more complex examples. Other plugins are written according to older, more complex rules and should not be used as models of how to write plugins.


CODE RESTRICTION

Your code must not contain any symbols which redefine symbols in ViSta. It is best if your code consists entirely of object-oriented code. In particular, avoid the use of DEFUN. If you must use DEFUN, make sure first that there isn't already a function with the same name. 



 |____________________________________________
 |
 | STEP 1: DEFINE THE PLUGIN SYSTEM VARIABLES
 |         AND PREPARE VISTA FOR YOUR PLUGIN
 |____________________________________________
 |

The following seven plugin system variables MUST be local variables that are bound as described:

1) PLUGIN-SUBDIRECTORY - A string of up to 8 characters specifying the name of the subdirectory containing the plugin code and the name of the file that contains the PLUGIN CONSTRUCTOR function. The filename must simultaneously follow the rules of directory naming for the MSDOS, MACINTOSH and UNIX operating systems.  

2) PLUGIN-FILE - A string of up to 8 characters specifying the name of the file that contains the PLUGIN CONSTRUCTOR function. The filename must simultaneously follow the rules of directory naming for the MSDOS, MACINTOSH and UNIX operating systems. ViSta uses the PLUGIN-FILE to construct the global system variable *NAME-plugin-path*, where NAME is replaced with the TOOLBAR-BUTTON specified below. 

The PLUGIN-FILE file MUST be located in the PLUGIN-SUBDIRECTORY subdirectory of the plugin directory. The file must contain the plugin's constructor function (which replaces the constructor loader function) and whatever other code is needed by the plugin. If all the code doesn't fit in one file, the plugin-constructor-file must load in the additional files that are needed.

3) MENU-ITEM - Specifies the plugin's menu item name (a string of up to about 20 characters).

4) TOOLBAR-BUTTON - Specifies the name of the plugin's button on the workmap's toolbar (a string of 5 or 6 characters).

5) WORKMAP-ICON-PREFIX - Specifies the plugin icon's name on the workmap (a string of 3 characters)

6) OK-VARIABLE-TYPES - Specifies the data types that are used by the plugin. The possible ok-variable-types are "numeric" and "category". The "ordinal" type is not supported.

7) OK-DATA-TYPES - Specifies the variable types that are used by the plugin (a string list). The eleven datatypes are divided into:
a) a datatype for data with missing values:
     "missing"
b) a datatype where the basic datum is a relation: 
     "matrix" 
c) six datatypes where the basic datum is a quantity: 
     "univariate" "bivariate" "multivariate" 
     "category"   "class"     "general"
d) three datatypes where the basic datum is a frequency: 
     "frequency"  "freqclass" "crosstabs"


The message (send $ :determine-data-type) determines and returns the datatype of $, the current data. More information about datatypes is available from the help topics."

These seven variables are then used to prepare ViSta's plugin environment for your plugin. The following statement defines the variables and prepares the environment for the frequency analyis module:


(let* ((plugin-subdirectory "freq")
       (plugin-file         "freqmain.lsp")
       (menu-item           "Frequency Analysis")
       (toolbar-button      "Freqs")
       (workmap-icon        "Frq")
       (data-types        '("class" "category" 
                            "freq" "freqclass" 
                            "crosstabs" "general"))
       (variable-types    '(numeric category))
       )

  (send *vista* :prepare-plugin-environment
        plugin-subdirectory
        plugin-file 
        menu-item 
        toolbar-button
        workmap-icon
        data-types 
        variable-types ))



ViSta uses PLUGIN-SUBDIRECTORY, PLUGIN-FILE and TOOLBAR-BUTTON to construct the global system variables *NAME-plugin-constructor-file* and *NAME-plugin-path*, where NAME is replaced with TOOLBAR-BUTTON. 

For example, assume that (where = means "bound to") 
  PLUGIN-SUBDIRECTORY = "freq",
  PLUGIN-FILE = "freqmain.lsp",and
  *PLUGIN-PATH* = "C:\Program Files\VisualStats\ViSta7\" 
then ViSta creates the following two global variables and binds them as shown:.
  *FRQNCY-PLUGIN-PATH* ="C:\Program Files\VisualStats\ViSta7\plugins\freq\"
  *FRQNCY-PLUGIN-CONSTRUCTOR-FILE* = "C:\Program Files\VisualStats\ViSta7\plugins\freq\freqmain.lsp"

 |____________________________________________
 |
 | ABOUT THE PLUGIN'S CONSTRUCTOR AND LOADER FUNCTIONS
 |____________________________________________
 |

You must write two functions. These two functions must have identical names and arguments. but they will have different bodies. One of these functions is loaded when ViSta is run, while the other is loadedthe first time the user uses the plugin. 

PLUGIN LOADER FUNCTION - The function that is loaded when ViSta is run is called the PLUGIN LOADER FUNCTION since it's primary purpose is to load the plugin's code the first time the user needs to use the plugin. The loader function is a small piece of code written according to rules specified below. It is contained in your plugin interface file (the file you place in the PLUGINS directory). 

PLUGIN-CONSTRUCTOR-FUNCTION
This function is the entry point to your plugin's code. The function issues the :ISNEW message which in turn constructs a new instance of the plugin, and then uses the new instance to do the analysis called for. The constructor function must be located in the PLUGIN CONSTRUCTOR file discussed earlier. The constructor file must also contain code to load in any additional code files.

The plugin system assumes that when the user selects an analysis menu item that there is a model constructor function which is the same as the menu item's name (but with spaces replaced by dashes). 

Thus, the names of the two functions must not only be identical, but they must also each be identical to MENU-ITEM specified above (with spaces in menu-item replaced by dashes in the function names).  

Because the loader and constructor functions have the same name, and because the code read in by the loader function contains the constructor function, the loader function modifies itself to become the constructor function (i.e., the loader function is self-modifying). The architecture of the constructor function is as it is so that code to do the analysis is not loaded until needed. Note, however, that the menu item, tool and model prefix are automatically loaded at startup.

Here are the rules for writing the loader and constructor function.
    (a) The loader and constructor functions MUST have the same names.
    (b) The loader/constructor name must be the same as the plugin's item name in the analysis menu (with menu-item spaces replaced by function-name dashes).
    (c) The loader and constructor functions MUST have the same arguments. The arguments must be keyword arguments.
    (d) The loader and constructor functions MUST have the following two keyword arguments:
           DATA    a symbol which, by default, 
                   is bound to *current-data* 
           DIALOG  a logical bound whose value 
                   must be NIL, even if the 
                   analysis dialog is to shown
    (e) The loader and the constructor function MAY have any number of additional keyword arguments as is needed by your analysis. 
    (f) The entire set of required and additional arguments must be the same for the loader and constructor functions.
 


 |____________________________________________
 |
 | STEP 2: DEFINE THE PLUGIN'S LOADER FUNCTION
 |____________________________________________
 |


The loader function is located in the PLUGIN LOADER FILE which is itself located in the PLUGINS directory. It's filename must follow the usual naming conventions. The function MUST take these following three actions. The actions MUST take place in the order specified:
    (a) display a copyright notice (optional, of course)
    (b) load code containing the constructor function
    (c) invoke the constructor function. 

Here is the plugin-loader-function for the frequency analysis module:

  
  (defun frequency-analysis 
    (&key
     (table-variables nil)
     (control-variables nil)
     (data   *current-data*)
     (dialog nil))
    "Args: &key table-variables control-variables data title name dialog
ViSta Frequency Analysis Plugin to calculate chi-square and related statistics for n-way frequency data. The analysis is based on the frequency array for the :TABLE-VARIABLES (a variable name string or a list of variable name strings). By default, the first two (or one) ways of the frequency array become the table variables. Additional ways are treated as the control variables, unless they are explicitly specified as :CONTROL-VARIABLES (a variable name string or a list of variable name strings). The remaining keywords have their usual meaning."
    (format t "; CopyRt: FREQ Copyright (c) 1998-2002, by Forrest W. Young & Dominic Moore~%> ")
    (load *freqs-plugin-constructor-file*)
    (frequency-analysis
     :table-variables   table-variables
     :control-variables control-variables
     :data   data
     :dialog dialog))


 |____________________________________________
 |
 | STEP 3: DEFINE THE PLUGIN'S CONSTRUCTOR FUNCTION
 |____________________________________________
 |

The constructor function must be located in the file specified by *PLUGIN-FILE* which must be located in the subdirectory specified by PLUGIN-SUBDIRECTORY, The function MUST have name and arguments that are identical to those of the loader function. The function does just one thing: It invokes the plugin's NEW method. 

Here is the plugin-constructor function for frequency analysis:


  (defun frequency-analysis
    (&key
     (table-variables nil)
     (control-variables nil)
     (data   *current-data*)
     (dialog nil))
    "Args: &key table-variables control-variables data title dialog
ViSta Frequency Analysis Plugin to calculate chi-square and related statistics for n-way frequency data. The analysis is based on the frequency array for the :TABLE-VARIABLES (a variable name string or a list of variable name strings). By default, the first two (or one) ways of the frequency array become the table variables. Additional ways are treated as the control variables, unless they are explicitly specified as :CONTROL-VARIABLES (a variable name string or a list of variable name strings). The remaining keywords have their usual meaning."
    (send freq-plugin-object-proto :new "Frequency Analysis" 
          data dialog table-variables control-variables))


 |____________________________________________
 |
 | STEP 4: DEFINE YOUR PLUGIN's PROTOTYPE OBJECT 
 |____________________________________________
 |

Your plugin's defproto function must define your proto in such a way as that it inherits from vista-analysis-plugin-object-proto. Here is the frequency analysis defproto statement:

(defproto freq-plugin-object-proto 
  '(table-vars control-vars freq-var nways chisq 
               phi binomial cmh 
               table-labels control-labels 
               observed-data-matrices expected-data-matrices 
               matrix-index-list freqclass-data-matrix) 
   () 
   vista-analysis-plugin-object-proto)



 |____________________________________________
 |
 | STEP 5: DEFINE YOUR PLUGIN'S :ISNEW METHOD
 |____________________________________________
 |


The plugin constructor function issues the :NEW message. This message invokes the plugin's isnew method which proceeds to create a new instance of the plugin object. 

Here are the rules for writing the plugin's :new message and its corresponding :isnew method: 
   (a) The new message and the :isnew method MUST have at least three arguments. The first three arguments must be, in order: (a) A string which is identical to MENU-ITEM. (b) DATA; and (c) DIALOG
   (b) You can follow the initial three arguments with whatever arguments are required for your plugin.  
   (c) These additional arguments must be processed (or stored for later processin) within the isnew method.
   (d) A CALL-NEXT-METHOD function must appear at the end of the constructor function, It must have exactly three arguments: MENU-ITEM, DATA, and DIALOG, in that order.

Here is the isnew method for frequency analysis:


(defmeth freq-plugin-object-proto :isnew 
                (title data dialog table-variables control-variables)
  (unless (equal data *current-data*)(setcd data))
  (unless (send data :array) 
          (error-message "Wrong kind of data for frequency analysis."))
  (let* ((vars (send data :active-array-variables))
         (nvars (length vars))
         (ntvars (min nvars 2))
         (1col (and (= 2 (length (array-dimensions (send data :data-array))))
                    (= 1 (second (array-dimensions (send data :data-array))))))
         )
    (flet ((check-vars (check-variables vars type)
            (when (stringp check-variables) 
                  (setf check-variables (list check-variables)))
            (when (not (listp check-variables))
                  (fatal-message "~a variables must be specified as either a variable name string or as a list of one or two variable name strings." type))
            (mapcar #'(lambda (var)
                  (if (not (member var vars))
                      (fatal-message "The variable ~s, specified as a ~a variable, is not found in these data." type))  check-variables))))
      (cond
        (table-variables
         (check-vars table-variables vars "Table")
         (when (length (> table-variables 2)) 
               (fatal-message "Too many table variables. Maximum is 2.")))
        ( t (setf table-variables (select vars (iseq ntvars)))))
      (if control-variables
          (check-vars control-variables vars "Control")
          (when (> ntvars nvars)
                (setf control-variables (select vars (iseq ntvars (1- nvars))))))
      (send self :table-vars table-variables)
      (send self :control-vars control-variables)
      (send self :freq-var (send self :freq-var? data))
      
      (call-next-method title data dialog)
      )))  
